using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using Tron.Players;
using Tron.Animations;
using Tron.Players.Simple;
using Tron.Graphics;

namespace Tron
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class TronGame : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        TextGraphics textGraphics;
        public TextGraphics TextGraphics
        {
            get
            {
                return textGraphics;
            }
        }

        PlayerManager playerManager;
        Board board;

        RepeatGameTypeScoreHelper repeatGameTypeScoreHelper;

        public TronGame()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            graphics.PreferredBackBufferWidth = Constants.WindowWidth;
            graphics.PreferredBackBufferHeight = Constants.WindowHeight;
            graphics.IsFullScreen = Constants.IsFullScreen;
        }

        protected override void OnExiting(object sender, EventArgs args)
        {
            foreach (Player player in playerManager.Players)
            {
                player.SaveResults();
            }
            base.OnExiting(sender, args);
        }
        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            #region Verify validity of some of the Constants values.
            int playerCount = Constants.PlayerColors.Count;

            if (playerCount != Constants.PlayerColors.Count ||
                playerCount != Constants.PlayerTypes.Count ||
                playerCount != Constants.PlayerStartingPositions.Count ||
                playerCount != Constants.PlayerStartingDirections.Count ||
                playerCount != Constants.PlayerNames.Count)
            {
                throw new Exception("PlayerColors, PlayerTypes, PlayerStartingPositions, PlayerStartingDirections, and PlayerNames are not all the same length!");
            }

            int humanPlayerCount = 0;
            foreach (PlayerTypesEnum playerType in Constants.PlayerTypes)
            {
                if (playerType == PlayerTypesEnum.Human)
                {
                    humanPlayerCount++;
                    break;
                }
            }
            if (humanPlayerCount == 0 && Constants.GameType == GameTypesEnum.TurnBased)
            {
                throw new Exception("The TurnBased game mode was selected but there is no human player.");
            }
            if (humanPlayerCount > 1)
            {
                throw new Exception("This game currently only supports one human player.");
            }

            if (Constants.GameType == GameTypesEnum.Repeat)
            {
                if (Constants.RepeatTimes <= 0)
                {
                    throw new Exception("RepeatTimes must be greater than 0!");
                }
            }
            #endregion

            board = new Board(this, Constants.Rows, Constants.Columns, Constants.PlayerColors);
            playerManager = new PlayerManager(this, Constants.PlayerColors, Constants.PlayerTypes,
                Constants.PlayerStartingPositions, Constants.PlayerStartingDirections, board, true, null, Constants.PlayerNames);

            List<BlockAnimator> playerBlockAnimators = new List<BlockAnimator>();
            List<BlockGraphics> playerBlockGraphics = new List<BlockGraphics>();
            foreach (PlayerState playerState in playerManager.PlayerStates)
            {
                playerBlockAnimators.Add(playerState.BlockAnimator);
                playerBlockGraphics.Add(new BlockGraphics(this, Vector2.Zero, playerState.Color));
                playerBlockGraphics[playerBlockGraphics.Count - 1].Style = BlockGraphics.Styles.HeavyEdges;
            }
            board.SetPlayerBlockStuff(playerBlockGraphics, playerBlockAnimators);

            SpriteFont spriteFont = Content.Load<SpriteFont>("SpriteFonts\\Kootenay");

            base.Initialize();

            textGraphics = new TextGraphics(spriteBatch, spriteFont, Vector2.Zero);
            textGraphics.Color = Color.Red;

            repeatGameTypeScoreHelper = new RepeatGameTypeScoreHelper();

            repeatTimesLeft--;
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            // TODO: use this.Content to load your game content here
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        int stepsTaken = 0;
        double humanPlayerCrashTime = 0;
        int gamesPlayed = 0;
        int repeatTimesLeft = Constants.RepeatTimes;

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed ||
                Keyboard.GetState().IsKeyDown(Keys.Escape))
            {
                this.Exit();
            }

            // TODO: Add your update logic here
            switch (Constants.GameType)
            {
                case GameTypesEnum.Standard:
                    int currentStep = (int)(gameTime.TotalGameTime.TotalSeconds / Constants.StepDuration);
                    while (currentStep > stepsTaken)
                    {
                        stepsTaken++;
                        playerManager.Step();
                    }
                    break;
                case GameTypesEnum.TurnBased:
                    HumanPlayer humanPlayer = null;
                    foreach (Player player in playerManager.Players)
                    {
                        if (player is HumanPlayer)
                        {
                            humanPlayer = (HumanPlayer)player;
                        }
                    }
                    if (playerManager.PlayerStates[humanPlayer.PlayerIndex].Alive)
                    {
                        if (!humanPlayer.WaitingForKeyUp)
                        {
                            if (!humanPlayer.WaitingForKeyDown)
                            {
                                //stepsTaken++;
                                playerManager.Step();
                            }
                        }
                    }
                    else
                    {
                        if (humanPlayerCrashTime == 0)
                        {
                            humanPlayerCrashTime = gameTime.TotalGameTime.TotalSeconds;
                        }
                        currentStep = (int)((gameTime.TotalGameTime.TotalSeconds - humanPlayerCrashTime)/ Constants.StepDuration);
                        while (currentStep > stepsTaken)
                        {
                            stepsTaken++;
                            playerManager.Step();
                        }
                    }
                    break;
                case GameTypesEnum.Repeat:
                    if (repeatTimesLeft >= 0)
                    {
                        if (repeatTimesLeft == Constants.RepeatTimes - 1)
                        {
                            // This is the first game.
                        }
                        else
                        {
                            // This is a subsequent game.  Reset the player manager.
                            playerManager = new PlayerManager(this, Constants.PlayerColors, Constants.PlayerTypes,
                                Constants.PlayerStartingPositions, Constants.PlayerStartingDirections, board, false, playerManager.Players,
                                Constants.PlayerNames);

                            board = new Board(this, Constants.Rows, Constants.Columns, Constants.PlayerColors);
                            List<BlockAnimator> playerBlockAnimators = new List<BlockAnimator>();
                            List<BlockGraphics> playerBlockGraphics = new List<BlockGraphics>();
                            foreach (PlayerState playerState in playerManager.PlayerStates)
                            {
                                playerBlockAnimators.Add(playerState.BlockAnimator);
                                playerBlockGraphics.Add(new BlockGraphics(this, Vector2.Zero, playerState.Color));
                                playerBlockGraphics[playerBlockGraphics.Count - 1].Style = BlockGraphics.Styles.HeavyEdges;
                            }
                            board.SetPlayerBlockStuff(playerBlockGraphics, playerBlockAnimators);
                        }
                        do
                        {
                            playerManager.Step();
                        } while (playerManager.AliveCount > 0);

                        gamesPlayed++;
                        repeatGameTypeScoreHelper.AddScoresToTotals(playerManager.PlayerScores);
                        textGraphics.Text = "Games played: " + gamesPlayed + "\n" +
                            "Games remaining: " + repeatTimesLeft + "\n" + repeatGameTypeScoreHelper.ToString();
                        repeatTimesLeft--;
                    }
                    break;
            }

            foreach (Player player in playerManager.Players)
            {
                if (player is HumanPlayer)
                    (player as HumanPlayer).UpdateInput();
            }

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.Black);

            if (Constants.GameType != GameTypesEnum.Repeat)
            {
                board.Draw(gameTime, spriteBatch);
            }

            textGraphics.Draw();
            base.Draw(gameTime);
        }
    }
}
